package service

import (
    "fmt"
    "ginBlog/utils"
    "strconv"
    "time"
)

type Article struct {
    ID          int     `json:"id" db:"id"`
    Title       string  `json:"title" db:"title"`
    Content     string  `json:"content" db:"content" binding:"required"`
    //Html        string  `json:"html" db:"html" binding:"required"`
    CategoryID  int     `json:"category_id" db:"category_id" binding:"required"`
    TagID       []int   `json:"tag_id" binding:"required"`
    CreatedTime string `json:"created_time" db:"created_time"`
    UpdatedTime string `json:"updated_time" db:"updated_time"`
    Status      string  `json:"status" db:"status" binding:"required"`
}

type Articles struct {
    Items []Article `json:"items"`
    Total int       `json:"total"`
}

type ArticleDetail struct {
    A       Article   `json:"article"`
    C       Category  `json:"category"`
    Tags    []Tag     `json:"tags"`
    //Views   uint8     `json:"views"`
}

type Options struct {
    Limit   int     `json:"limit"`
    Page    int     `json:"page"`
    Search  bool    `json:"search"`
    Admin   bool    `json:"admin"`
    C       string  `json:"c"` // category
    T       string  `json:"t"`  // tag
    Q       string  `json:"q"`  // 搜索的关键字
}


var defaultOptions = Options{
    Limit: 10,
    Page: 1,
    C: "",
    T: "",
    Q: "",
    Search: false, // 搜索文章结果不进行缓存
    Admin: false, // 是否是admin页面请求，如果不是，文章就不包括草稿文章
}

type Option func(options *Options)

func newOptions(opts ...Option) Options {
    // 初始化默认值
    opt := defaultOptions
    for _, o := range opts {
        o(&opt) // 依次调用opts函数列表中的函数，为服务选项（opt变量）赋值
    }
    return opt
}

func SetLimitPage(limit, page string) Option {
    return func(o *Options) {
        if limit != "" && page != "" {
            p, _ :=strconv.Atoi(page)
            l, _ := strconv.Atoi(limit)
            o.Limit = l
            o.Page = p
        }
    }
}

func SetAdmin(admin string) Option {
    return func(o *Options) {
        if admin != "" {
            o.Admin = true
        }
    }
}

func SetCategory(c string) Option {
    return func(o *Options) {
        o.C = c
    }
}

func SetTag(t string) Option {
    return func(o *Options) {
        o.T = t
    }
}

//func SetQ(q string) Option {
//    return func(o *Options) {
//        o.Q = q
//    }
//}

func SetSearch(search bool) Option {
    return func(o *Options) {
        o.Search = search
        o.Page = defaultOptions.Page // 如果不是在第一页执行的搜索，比如：page=3，有可能会搜不到数据，必须从第一页开始搜索
    }
}

func (a *Article) Create() (Article, error) {
    createTime := time.Now().Format(utils.AppInfo.TimeFormat)

    r, e := db.Exec("insert into blog_article (title,content,category_id,created_time,status) values (?,?,?,?,?)", a.Title, a.Content, a.CategoryID, createTime, a.Status)
    if e != nil {
        return Article{}, e
    }
    articleID, _ := r.LastInsertId()
    if len(a.TagID) > 0 {
        for _, tagID := range a.TagID{
            _, e := db.Exec("insert into blog_tag_article (tag_id, article_id) values (?, ?)", tagID, articleID)
            if e != nil {
                return Article{}, e
            }
        }
    }
    article := Article{int(articleID), a.Title, a.Content, a.CategoryID, a.TagID, createTime, a.UpdatedTime, a.Status}

    return article, nil
}

func (a *Article) Delete() error{
    if _, e := db.Exec("delete from blog_tag_article where article_id=?", a.ID) ; e != nil {
        return e
    }
    if _, e := db.Exec("delete from blog_article where id=?", a.ID); e != nil {
        return e
    }
    return nil
}

func (a *Article) Edit() error {

    updateTime := time.Now().Format(utils.AppInfo.TimeFormat)
    if _, e := db.Exec("update blog_article set title=?,content=?,category_id=?,updated_time=?,`status`=? where id=?", a.Title, a.Content, a.CategoryID, updateTime, a.Status, a.ID); e != nil {
        return e
    }

    if _, e := db.Exec("delete from blog_tag_article where article_id=?", a.ID); e != nil {
        return e
    }
    if len(a.TagID) >0 {
        for _, tagID := range a.TagID{
            _,e := db.Exec("insert into blog_tag_article (tag_id, article_id) values (?, ?)", tagID, a.ID)
            return e
        }
    }

    return nil
}

func (a Article) GetOne(opts ...Option) (ArticleDetail, error) {
    //options := newOptions(opts...)
    var one Article
    if e := db.Get(&one, "select * from blog_article where id=?", a.ID); e != nil {
        return ArticleDetail{}, e
    }
    category, _ := GetCategoryByID(one.CategoryID)
    tags, _ := GetTagsByArticleID(a.ID)

    return ArticleDetail{one, category, tags}, nil
}

func (a Article) GetArticle() (Article, error) {
    var article Article
    e := db.Get(&article, "select id,title from blog_article where id=?;", a.ID)
    return article, e
}

func (a Article) GetAll(opts ...Option) (data Articles, err error) {
    baseSql := "select %s from blog_article a"
    data, err = genArticles(baseSql, opts...)
    return
}

func GetArticlesByCategory(opts ...Option) (data Articles, err error) {
    options := newOptions(opts...)
    baseSql := "SELECT %s FROM blog_article a INNER JOIN blog_category c ON a.category_id=c.id AND c.category_name=" + "'" + options.C + "'" + ""
    data, err = genArticles(baseSql, opts...)
    return
}

func GetArticlesByTag(opts ...Option) (data Articles, err error) {
    options := newOptions(opts...)
    baseSql := "SELECT %s FROM blog_article a  INNER JOIN blog_tag_article ta ON a.id=ta.article_id INNER JOIN blog_tag t ON ta.tag_id=t.id AND t.tag_name=" + "'" + options.T + "'" + ""
    data, err = genArticles(baseSql, opts...)
    return
}

func SearchArticle(key, status string, opts ...Option) (data Articles, err error) {
    var baseSql string
    if status == "" {
        baseSql = `SELECT %s FROM blog_article a WHERE a.title LIKE '%%` + key + `%%'`
    } else {
        baseSql = `SELECT %s FROM blog_article a WHERE a.title LIKE '%%` + key + `%%' AND a.status='` + status + `'`
    }

    data, err = genArticles(baseSql, opts...)
    return
}

func genArticles(baseSql string, opts ...Option) (data Articles, err error) {
    options := newOptions(opts...)
    articles := make([]Article, 0)

    var f string
    if !options.Admin {
        f = " WHERE a.`status`='published'"
    }
    offset := (options.Page - 1) * options.Limit
    selectSql := fmt.Sprintf(baseSql, "a.id, a.title, a.content, a.created_time, a.updated_time, a.`status`") + f + fmt.Sprintf(" ORDER BY a.id DESC limit %d offset %d", options.Limit, offset)

    if err = db.Select(&articles, selectSql); err != nil {
        return
    }

    var total int
    if err = db.Get(&total, fmt.Sprintf(baseSql, "count(1)")+f); err != nil {
        return
    }

    data.Total = total
    data.Items = articles

    return
}

func GetCategoryByID(id int) (Category, error) {
    var c Category
    if e := db.Get(&c, "select * from blog_category where id=?;", id); e != nil{
        return Category{}, e
    }
    return c, nil
}

func GetTagsByArticleID(articleID int) ([]Tag, error)  {
    var t []Tag
    if e := db.Select(&t, "SELECT t.* FROM blog_tag t RIGHT JOIN blog_tag_article ta ON t.id=ta.tag_id WHERE ta.article_id=?", articleID); e != nil {
        return nil, e
    }
    return t, nil
}



